﻿using System;
using System.Threading;

namespace MathService
{
    /// <summary>Наблюдатель</summary>
    public class ObjectWacher 
    {
        /* ------------------------------------------------------------------------------------------ */

        /// <summary>Поток наблюдения запущен</summary>
        public event EventHandler Started;

        /// <summary>Поток наблюдения остановлен</summary>
        public event EventHandler Stoped;
        
        /// <summary>Зафиксирована ошибка в потоке наблюдателя</summary>
        public event ExceptionEventHandler<Exception> Error;

        /* ------------------------------------------------------------------------------------------ */

        /// <summary>Таймаут вызова метода наблюдения</summary>
        private int f_Timeuot;

        /// <summary>Поток выполнения метода наблюдения</summary>
        private Thread f_WachThread;

        /// <summary>Признак активности</summary>
        private bool f_Enabled;

        private Action f_WachMethod;

        private ThreadPriority f_Priority;

        /* ------------------------------------------------------------------------------------------ */

        /// <summary>Управление активностью наблюдателя</summary>
        public bool Enable { get { return f_Enabled; } set { if(value) Start(); else Stop(); } }

        /// <summary>Метод наблюдения</summary>
        public Action WachMethod { get { return f_WachMethod; } set { f_WachMethod = value; } }

        /// <summary>Приоритет выполнения метода наблюдения</summary>
        public ThreadPriority Priority
        {
            get { return f_Priority; }
            set
            {
                f_Priority = value;
                var lv_Thread = f_WachThread;
                lock(this)
                    if(lv_Thread != null && (lv_Thread.IsAlive || lv_Thread.IsBackground))
                        lv_Thread.Priority = value;
            }
        }

        /// <summary>Таймаут вызова метода наблюдения</summary>
        public int Timeuot { get { return f_Timeuot; } set { f_Timeuot = value; } }

        /* ------------------------------------------------------------------------------------------ */

        /// <summary>Инициализация наблюдателя</summary>
        /// <param name="Timeout">Таймаут потока наблюдения</param>
        /// <param name="WachMethod">Метод наблюдения</param>
        /// <param name="Priority">Приоритет потока наблюдения</param>
        public ObjectWacher(int Timeout = 0, Action WachMethod = null, ThreadPriority Priority = ThreadPriority.Normal)
        {
            Timeuot = Timeout;
            f_WachMethod = WachMethod;
            f_Priority = Priority;
        }

        /* ------------------------------------------------------------------------------------------ */

        /// <summary>Запуск процесса наблюдения</summary>
        public void Start()
        {
            if(f_Enabled) return;
            lock(this)
            {
                if(f_Enabled) return;
                f_WachThread = new Thread(Wach)
                {
                    Priority = f_Priority,
                    IsBackground = true,
                    Name = "Поток наблюдателя"
                };
                f_Enabled = true;
                f_WachThread.Start();
            }
            Started.Start(this, EventArgs.Empty);
        }

        /// <summary>Остановка метода наблюдения</summary>
        public void Stop()
        {
            if(!f_Enabled) return;
            lock(this)
            {
                if(!f_Enabled) return;
                f_Enabled = false;
                if(!f_WachThread.Join(2 * Timeuot))
                    f_WachThread.Abort();
                f_WachThread = null;
            }
            Stoped.StartAsync(this, EventArgs.Empty);
        }

        /// <summary>Метод наблюдения</summary>
        private void Wach()
        {
            try
            {
                while(f_Enabled)
                {
                    var lv_WachMethod = f_WachMethod;
                    if(lv_WachMethod != null) lv_WachMethod();

                    var lv_Timeout = Timeuot;
                    if(Timeuot > 0)
                        Thread.Sleep(Timeuot);
                }
            }
            catch(Exception error) { Error.ThrowIfUnhandled(this, error); }
            Stop();
        }

        /* ------------------------------------------------------------------------------------------ */
    }
}
